﻿using System;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Web.Security;
using SubSonic.Security.Data;


namespace SubSonic.Security{

    public class Helper{

        #region IIdentity and IPrincipal

        public static IIdentity GetIdentity(){
            return new SubSonic.Security.SubSonicIdentity(string.Empty, false);
        }

        public static IIdentity GetIdentity(String username, String password, MembershipPasswordFormat passwordFormat){ 
            SubSonicIdentity identity = null;

            if (ValidateUser(username, password, passwordFormat))
                identity = new SubSonicIdentity(username, true);
            else
                identity = new SubSonicIdentity(string.Empty, false);

            return identity;
        }


        #endregion

        #region Membership

        /// <summary>
        /// Pings DB for user row then validates password
        /// </summary>
        /// <param name="username">User's username</param>
        /// <param name="password">User's password (hashed, encrypted, or clear)</param>
        /// <param name="passwordFormat">Password Format from Membership provider</param>
        /// <returns>Whether the user is valid or not</returns>
        private static bool ValidateUser(string username, string password, MembershipPasswordFormat passwordFormat){
            User u = new User(User.Columns.Username, username);

            if (u.IsLoaded)
                return Helper.ValidatePassword(password, u.Password, passwordFormat);
            else
                return false;
        }
        /// <summary>
        /// Password checked against requirements
        /// </summary>
        /// <param name="password">entered password</param>
        /// <param name="minRequiredNonAlphanumericCharacters">Min characters not A-Z, 1-9</param>
        /// <param name="minRequiredPasswordLength">Minimum num characters</param>
        /// <param name="passwordStrengthRegularExpression">Password strength REGEX</param>
        /// <returns>Valid password for password strength requirements</returns>
        public static bool CheckPassword(string password, int minRequiredNonAlphanumericCharacters, int minRequiredPasswordLength, string passwordStrengthRegularExpression){
            if (password.Length < minRequiredPasswordLength)
                return false;

            int nonAlphaInPWD = 0;

            foreach (char ch in password)
                if (!SubSonic.Sugar.Validation.IsAlphaNumeric(ch.ToString()))
                    nonAlphaInPWD += 1;

            if (nonAlphaInPWD < minRequiredNonAlphanumericCharacters)
                return false;

            //if it's blank... we're golden
            if (passwordStrengthRegularExpression == string.Empty)
                return true;

            if (SubSonic.Utilities.Utility.IsRegexMatch(password, passwordStrengthRegularExpression))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Formats password for what is defined in the config
        /// </summary>
        /// <param name="password">Entered password</param>
        /// <param name="passFormat">Password format from the Membership provider configuration</param>
        /// <returns>Formated password</returns>
        public static string FormatPassword(string password, MembershipPasswordFormat passFormat){
            switch (passFormat){
                case MembershipPasswordFormat.Encrypted:
                    return EncryptPassword(password);
                case MembershipPasswordFormat.Hashed:
                    return HashPassword(password);
                case MembershipPasswordFormat.Clear:
                    return password;
                default:
                    return string.Empty;
            }
        }

        /// <summary>
        /// Checks to see whether the password matches
        /// what is in the database
        /// </summary>
        /// <param name="password">User entered password</param>
        /// <param name="Goodpassword">Password in database</param>
        /// <param name="passFormat">Password format from the Membership provider configuration</param>
        /// <returns>Whether the password matches</returns>
        public static bool ValidatePassword(string password, string Goodpassword, MembershipPasswordFormat passFormat){
            switch (passFormat){
                case MembershipPasswordFormat.Encrypted:
                    return (password == DecryptPassword(Goodpassword));
                case MembershipPasswordFormat.Hashed:
                    return (HashPassword(password) == Goodpassword);
                case MembershipPasswordFormat.Clear:
                    return (password == Goodpassword);
                default:
                    return false;
            }
        }

        #endregion

        #region Password Private

        private const string encryptKey = "subsonicisamaizingtosaytheleast";

        /// <summary>
        /// Encrypts a non-encrypted password
        /// (Used when passwordFormat is encrypted)
        /// </summary>
        /// <param name="password">Entered password</param>
        /// <returns>Encrypted password</returns>
        private static string EncryptPassword(string password){
            if (password == string.Empty)
                return password;

            byte[] keyArray;
            byte[] passwordArray = UTF8Encoding.UTF8.GetBytes(password);
            bool useHashing = true;

            string key = encryptKey;

            if (useHashing){
                MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
                keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
                hashmd5.Clear();
            }
            else{
                keyArray = UTF8Encoding.UTF8.GetBytes(key);
            }

            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
            tdes.Key = keyArray;
            tdes.Mode = CipherMode.ECB;
            tdes.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = tdes.CreateEncryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(passwordArray, 0, passwordArray.Length);
            tdes.Clear();

            return Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }

        /// <summary>
        /// Decrypts an encrypted password
        /// (Used when passwordFormat is encrypted)
        /// </summary>
        /// <param name="encryptedPassword"></param>
        /// <returns>Decrypted password</returns>
        private static string DecryptPassword(string encryptedPassword){
            if (encryptedPassword == string.Empty)
                return string.Empty;

            byte[] keyArray;
            byte[] passwordArray = Convert.FromBase64String(encryptedPassword);
            bool useHashing = true;

            string key = encryptKey;

            if (useHashing){
                MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
                keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
                hashmd5.Clear();
            }
            else{
                keyArray = UTF8Encoding.UTF8.GetBytes(key);
            }

            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
            tdes.Key = keyArray;
            tdes.Mode = CipherMode.ECB;
            tdes.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = tdes.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(passwordArray, 0, passwordArray.Length);
            tdes.Clear();

            return UTF8Encoding.UTF8.GetString(resultArray);
        }

        /// <summary>
        /// Creates a hash for a non-hashed password
        /// (Used when passwordFormat is hashed)
        /// </summary>
        /// <param name="password">Password entered by user</param>
        /// <returns>Hashed password</returns>
        private static string HashPassword(string password){
            byte[] dataToHash = Encoding.UTF8.GetBytes(password);
            byte[] hashedPassword;
            SHA256 shaAlgorithmClass = new SHA256Managed();
            hashedPassword = shaAlgorithmClass.ComputeHash(dataToHash);
            return Convert.ToBase64String(hashedPassword);
        }

        #endregion

        public struct Cookies
        {
            public const string cookieUsername = "ss_UserName";
            public const string cookiePassword = "ss_Password";
            public const string cookieCacheUser = "ss_CacheUser";
            public const string cookiePasFormat = "ss_pf";
        }
    }
}
